/*
 * Bytecode Analysis Framework
 * Copyright (C) 2005,2008 University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.detect;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.IntAnnotation;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.StringAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.Path;
import edu.umd.cs.findbugs.ba.PathVisitor;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.obl.Obligation;
import edu.umd.cs.findbugs.ba.obl.ObligationAcquiredOrReleasedInLoopException;
import edu.umd.cs.findbugs.ba.obl.ObligationDataflow;
import edu.umd.cs.findbugs.ba.obl.ObligationFactory;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabase;
import edu.umd.cs.findbugs.ba.obl.State;
import edu.umd.cs.findbugs.ba.obl.StateSet;
import edu.umd.cs.findbugs.ba.type.TypeDataflow;
import edu.umd.cs.findbugs.ba.type.TypeFrame;
import edu.umd.cs.findbugs.bcel.CFGDetector;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;

/**
 * Find unsatisfied obligations in Java methods.
 * Examples: open streams, open database connections, etc.
 *
 * <p>See Weimer and Necula,
 * <a href="http://doi.acm.org/10.1145/1028976.1029011"
 * >Finding and preventing run-time error handling mistakes</a>,
 * OOPSLA 2004.</p>
 *
 * @author David Hovemeyer
 */
public class FindUnsatisfiedObligation extends CFGDetector {

	private static final boolean DEBUG = SystemProperties.getBoolean("oa.debug");
	private static final String DEBUG_METHOD = SystemProperties.getProperty("oa.method");
	private static final boolean DEBUG_FP = SystemProperties.getBoolean("oa.debug.fp");

	/**
	 * Compute possible obligation transfers as a way of
	 * suppressing false positives due to "wrapper" objects.
	 * Not quite ready for prime time.
	 */
	private static final boolean COMPUTE_TRANSFERS = SystemProperties.getBoolean("oa.transfers", true);

	/**
	 * Report path information from point of resource creation
	 * to CFG exit.  This makes the reported warning a lot easier
	 * to understand.
	 */
	private static final boolean REPORT_PATH = SystemProperties.getBoolean("oa.reportpath", true);

	private static final boolean REPORT_PATH_DEBUG = SystemProperties.getBoolean("oa.reportpath.debug");

	/**
	 * Report the final obligation set as part of the BugInstance.
	 */
	private static final boolean REPORT_OBLIGATION_SET = SystemProperties.getBoolean("oa.report.obligationset", true);

	private final BugReporter bugReporter;
	private ObligationPolicyDatabase database;

	public FindUnsatisfiedObligation(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
		 IAnalysisCache analysisCache = Global.getAnalysisCache();
			
		database = analysisCache.getDatabase(ObligationPolicyDatabase.class);
		
	}

	@Override
    public void visitClass(ClassDescriptor classDescriptor) throws CheckedAnalysisException {
		IAnalysisCache analysisCache = Global.getAnalysisCache();

		ObligationFactory factory = database.getFactory();
			
		JavaClass jclass = analysisCache.getClassAnalysis(JavaClass.class, classDescriptor);
		for(Constant c : jclass.getConstantPool().getConstantPool()) {
			if (c instanceof ConstantNameAndType) {
				ConstantNameAndType cnt = (ConstantNameAndType) c;
				String signature = cnt.getSignature(jclass.getConstantPool());
				if (factory.signatureInvolvesObligations(signature)) {
					super.visitClass(classDescriptor);
					return;
				}
			} else if (c instanceof ConstantClass) {
				String className = ((ConstantClass)c).getBytes(jclass.getConstantPool());
				if (factory.signatureInvolvesObligations(className)) {
					super.visitClass(classDescriptor);
					return;
				}
			}
		}
		if (DEBUG) 
			System.out.println(classDescriptor + " isn't interesting for obligation analysis");
	}
	@Override
	protected void visitMethodCFG(MethodDescriptor methodDescriptor, CFG cfg) throws CheckedAnalysisException {
		
		MethodChecker methodChecker = new MethodChecker(methodDescriptor, cfg);
		methodChecker.analyzeMethod();
	}

	/**
	 * Helper class to keep track of possible obligation transfers
	 * observed along paths where an obligation appears to be leaked.
	 */
	private static class PossibleObligationTransfer {
		Obligation consumed, produced;

		public PossibleObligationTransfer(@Nonnull Obligation consumed, @Nonnull Obligation produced) {
			this.consumed = consumed;
			this.produced = produced;
		}

		/**
		 * Determine whether the state has "balanced" obligation
		 * counts for the consumed and produced Obligation types.
		 *
		 * @param state a State
		 * @return true if the obligation counts are balanced,
		 *         false otherwise
		 */
		private boolean balanced(State state) {
			int consumedCount = state.getObligationSet().getCount(consumed.getId());
			int producedCount = state.getObligationSet().getCount(produced.getId());
			return (consumedCount + producedCount == 0) && (consumedCount == 1 || producedCount == 1);
		}

		private boolean matches(Obligation possiblyLeakedObligation) {
			return consumed.equals(possiblyLeakedObligation) || produced.equals(possiblyLeakedObligation);
		}

		@Override
		public String toString() {
			return consumed + " -> " + produced;
		}
	}

	/**
	 * A helper class to check a single method for unsatisfied obligations.
	 * Avoids having to pass millions of parameters to each method
	 * (type dataflow, null value dataflow, etc.).
	 */
	private class MethodChecker {
		MethodDescriptor methodDescriptor;
		CFG cfg;
		IAnalysisCache analysisCache;
		ObligationDataflow dataflow;
		ConstantPoolGen cpg;
		TypeDataflow typeDataflow;
		Subtypes2 subtypes2;
		XMethod xmethod;

		MethodChecker(MethodDescriptor methodDescriptor, CFG cfg) {
			this.methodDescriptor = methodDescriptor;
			this.cfg = cfg;
		}

		public void analyzeMethod() throws CheckedAnalysisException {
			if (DEBUG_METHOD != null && !methodDescriptor.getName().equals(DEBUG_METHOD)) {
				return;
			}

			

			if (DEBUG) {
				System.out.println("*** Analyzing method " + methodDescriptor);
			}

			xmethod = XFactory.createXMethod(methodDescriptor);
			analysisCache = Global.getAnalysisCache();

			//
			// Execute the obligation dataflow analysis
			//
			try {
				dataflow = analysisCache.getMethodAnalysis(ObligationDataflow.class, methodDescriptor);
			} catch (ObligationAcquiredOrReleasedInLoopException e) {
				// It is not possible to analyze this method.
				if (DEBUG) {
					System.out.println("FindUnsatisifedObligation: " + methodDescriptor + ": " + e.getMessage());
				}
				return;
			}

			//
			// Additional analyses
			// needed these to apply the false-positive
			// suppression heuristics.
			//
			cpg = analysisCache.getClassAnalysis(ConstantPoolGen.class, methodDescriptor.getClassDescriptor());
			typeDataflow = analysisCache.getMethodAnalysis(TypeDataflow.class, methodDescriptor);
			subtypes2 = Global.getAnalysisCache().getDatabase(Subtypes2.class);

			//
			// Main loop: looking at the StateSet at the exit block of the CFG,
			// see if there are any states with nonempty obligation sets.
			//
			Map<Obligation, State> leakedObligationMap = new HashMap<Obligation, State>();
			StateSet factAtExit = dataflow.getResultFact(cfg.getExit());
			for (Iterator<State> i = factAtExit.stateIterator(); i.hasNext();) {
				State state = i.next();
				checkStateForLeakedObligations(state, leakedObligationMap);
			}

			//
			// Report a separate BugInstance for each Obligation,State pair.
			// (Two different obligations may be leaked in the same state.)
			//
			for (Map.Entry<Obligation, State> entry : leakedObligationMap.entrySet()) {
				Obligation obligation = entry.getKey();
				State state = entry.getValue();
				reportWarning(obligation,state);
			}
			// TODO: closing of nonexistent resources

		}

		private void checkStateForLeakedObligations(State state, Map<Obligation, State> leakedObligationMap) throws IllegalStateException {
			if (DEBUG) {
				Path path = state.getPath();
				if (path.getLength() > 0 && path.getBlockIdAt(path.getLength() - 1) != cfg.getExit().getLabel()) {
					throw new IllegalStateException("path " + path + " at cfg exit has no label for exit block");
				}
			}

			for (int id = 0; id < database.getFactory().getMaxObligationTypes(); ++id) {
				Obligation obligation = database.getFactory().getObligationById(id);
				// If the raw count produced by the analysis
				// for this obligation type is 0,
				// assume everything is ok on this state's path.
				int rawLeakCount = state.getObligationSet().getCount(id);
				if (rawLeakCount == 0) {
					continue;
				}

				// Apply the false-positive suppression heuristics
				int leakCount;
				try {
					leakCount = getAdjustedLeakCount(state, id);
				} catch (DataflowAnalysisException e) {
					// ignore
					continue;
				} catch (ClassNotFoundException e) {
					// ignore
					continue;
				}

				if (leakCount > 0) {
					leakedObligationMap.put(obligation, state);
				}
				// TODO: if the leak count is less than 0, then a nonexistent resource was closed
			}
		}

		private void reportWarning(Obligation obligation, State state) {
			String className = obligation.getClassName();
			
			if (methodDescriptor.isStatic() && methodDescriptor.getName().equals("main")
					&& methodDescriptor.getSignature().equals("([Ljava/lang/String;)V") 
					&& (className.contains("InputStream") ||  className.contains("Reader"))) {
				// Don't report unclosed input streams and readers in main() methods
				return;
			}
			BugInstance bugInstance = new BugInstance(FindUnsatisfiedObligation.this, "OBL_UNSATISFIED_OBLIGATION", NORMAL_PRIORITY)
				.addClassAndMethod(methodDescriptor).addClass(className)
				.describe("CLASS_REFTYPE");

			// Report how many instances of the obligation are remaining
			bugInstance
				.addInt(state.getObligationSet().getCount(obligation.getId()))
				.describe(IntAnnotation.INT_OBLIGATIONS_REMAINING);

			// Add source line information
			annotateWarningWithSourceLineInformation(state, obligation, bugInstance);

			if (REPORT_OBLIGATION_SET) {
				bugInstance.addString(state.getObligationSet().toString()).describe(StringAnnotation.REMAINING_OBLIGATIONS_ROLE);
			}

			bugReporter.reportBug(bugInstance);
		}

		private void annotateWarningWithSourceLineInformation(State state, Obligation obligation, BugInstance bugInstance) {
			// The reportPath() method currently does all reporting
			// of source line information.
			if (REPORT_PATH) {
				reportPath(bugInstance, obligation, state);
			}
		}

		/**
		 * Helper class to apply the false-positive suppression heuristics
		 * along a Path where an obligation leak might have occurred.
		 */
		private class PostProcessingPathVisitor implements PathVisitor {
			Obligation possiblyLeakedObligation;
			State state;
			int adjustedLeakCount;
			BasicBlock curBlock;
			boolean couldNotAnalyze;
			List<PossibleObligationTransfer> transferList;

			public PostProcessingPathVisitor(Obligation possiblyLeakedObligation/*, int initialLeakCount*/, State state) {
				this.possiblyLeakedObligation = possiblyLeakedObligation;
				this.state = state;
				this.adjustedLeakCount = state.getObligationSet().getCount(possiblyLeakedObligation.getId());
				if (COMPUTE_TRANSFERS) {
					this.transferList = new LinkedList<PossibleObligationTransfer>();
				}
			}

			public int getAdjustedLeakCount() {
				return adjustedLeakCount;
			}

			public boolean couldNotAnalyze() {
				return couldNotAnalyze;
			}

			public void visitBasicBlock(BasicBlock basicBlock) {
				curBlock = basicBlock;

				if (COMPUTE_TRANSFERS && basicBlock == cfg.getExit()) {
					// We're at the CFG exit.

					if (adjustedLeakCount == 1) {
						applyPossibleObligationTransfers();
					}
				}
			}

			public void visitInstructionHandle(InstructionHandle handle) {
				try {
					Instruction ins = handle.getInstruction();
					short opcode = ins.getOpcode();

					if (opcode == Constants.PUTFIELD || opcode == Constants.PUTSTATIC || opcode == Constants.ARETURN) {
						//
						// A value is being assigned to a field or returned from
						// the method.
						//
						Location loc = new Location(handle, curBlock);
						TypeFrame typeFrame = typeDataflow.getFactAtLocation(loc);
						if (!typeFrame.isValid()) {
							// dead code?
							couldNotAnalyze = true;
						}
						Type tosType = typeFrame.getTopValue();
						if (tosType instanceof ObjectType && isPossibleInstanceOfObligationType(subtypes2, (ObjectType) tosType, possiblyLeakedObligation.getType())) {
							// Remove one obligation of this type
							adjustedLeakCount--;
						}
					}

					if (COMPUTE_TRANSFERS && ins instanceof InvokeInstruction) {
						checkForPossibleObligationTransfer((InvokeInstruction) ins, handle);
					}
				} catch (ClassNotFoundException e) {
					bugReporter.reportMissingClass(e);
					couldNotAnalyze = true;
				} catch (DataflowAnalysisException e) {
					couldNotAnalyze = true;
				}
			}

			private void applyPossibleObligationTransfers() {
				//
				// See if we recorded any possible obligation transfers
				// that might have created a "wrapper" object.
				// In many cases, it is correct to close either
				// the "wrapped" or "wrapper" object.
				// So, if we see a possible transfer, and we see
				// a +1/-1 obligation count for the pair
				// (consumed and produced obligation types),
				// rather than 0/0,
				// then we will assume that which resource was closed
				// (wrapper or wrapped) was the opposite of what
				// we expected.
				//
				for (PossibleObligationTransfer transfer : transferList) {
					if (DEBUG_FP) {
						System.out.println("Checking possible transfer " + transfer + "...");
					}

					boolean matches = transfer.matches(possiblyLeakedObligation);

					if (DEBUG_FP) {
						System.out.println("  matches: " + possiblyLeakedObligation);
					}

					if (matches) {
						boolean balanced = transfer.balanced(state);
						if (DEBUG_FP) {
							System.out.println("  balanced: " + balanced + " in " + state.getObligationSet());
						}
						if (balanced) {
							if (DEBUG_FP) {
								System.out.println("  Suppressing path because "
									+ "a transfer appears to result in balanced "
									+ "outstanding obligations");
							}

							adjustedLeakCount = 0;
							break;
						}
					}
				}
			}

			private void checkForPossibleObligationTransfer(InvokeInstruction inv, InstructionHandle handle) throws ClassNotFoundException {
				//
				// We will assume that a method invocation might transfer
				// an obligation from one type to another if
				// 1. either
				//    - it's a constructor where the constructed
				//      type and exactly one param type
				//      are obligation types, or
				//   - it's a method where the return type and
				//      exactly one param type are obligation types
				// 2. at least one instance of the resource "consumed"
				//    by the transfer exists at the point of the transfer.
				//    E.g., if we see a transfer of InputStream->Reader,
				//    there must be an instance of InputStream at
				//    the transfer point.
				//

				if (DEBUG_FP) {
					System.out.println("Checking " + handle + " as possible obligation transfer...:");
				}

				// Find the State which is a prefix of the error state
				// at the location of this (possible) transfer.
				State transferState = getTransferState(handle);
				if (transferState == null) {
					if (DEBUG_FP) {
						System.out.println("No transfer state???");
					}
					return;
				}

				String methodName = inv.getMethodName(cpg);
				Type producedType = methodName.equals("<init>") ? inv.getReferenceType(cpg) : inv.getReturnType(cpg);

				if (DEBUG_FP && !(producedType instanceof ObjectType)) {
					System.out.println("Produced type " + producedType + " not an ObjectType");
				}

				if (producedType instanceof ObjectType) {
					Obligation produced = database.getFactory().getObligationByType((ObjectType) producedType);

					if (DEBUG_FP && produced == null) {
						System.out.println("Produced type  " + producedType + " not an obligation type");
					}

					if (produced != null) {
						XMethod calledMethod = XFactory.createXMethod(inv, cpg);
						Obligation[] params = database.getFactory().getParameterObligationTypes(calledMethod);

						for (int i = 0; i < params.length; i++) {
							Obligation consumed = params[i];

							if (DEBUG_FP && consumed == null) {
								System.out.println("Param " + i + " not an obligation type");
							}

							if (DEBUG_FP && consumed != null && consumed.equals(produced)) {
								System.out.println("Consumed type is the same as produced type");
							}

							if (consumed != null && !consumed.equals(produced)) {
								// See if an instance of the consumed obligation type
								// exists here.
								if (transferState.getObligationSet().getCount(consumed.getId()) > 0) {
									transferList.add(new PossibleObligationTransfer(consumed, produced));
									if (DEBUG_FP) {
										System.out.println("===> Possible transfer of " + consumed + " to " + produced + " at " + handle);
									}
								} else if (DEBUG_FP) {
									System.out.println(handle + " not a transfer " +
										"of " + consumed + "->" + produced +
										" because no instances of " + consumed);
									System.out.println("I see " + transferState.getObligationSet());
								}
							}
						}
					}
				}
			}

			public void visitEdge(Edge edge) {
				if (DEBUG_FP) {
					System.out.println("visit edge " + edge);
				}
			}

			private State getTransferState(InstructionHandle handle) {
				StateSet stateSet;
				try {
					stateSet = dataflow.getFactAtLocation(new Location(handle, curBlock));
				} catch (DataflowAnalysisException e) {
					bugReporter.logError("Error checking obligation state at " + handle, e);
					return null;
				}

				List<State> prefixes = stateSet.getPrefixStates(state.getPath());
				if (prefixes.size() != 1) {
					// Could this happen?
					if (DEBUG_FP) {
						System.out.println("at " + handle + " in " + xmethod
							+ " found " + prefixes.size() + " states which are prefixes of error state");
					}
					return null;
				}

				return prefixes.get(0);
			}
		}

		/**
		 * Get the adjusted leak count for the given State and obligation type.
		 * Use heuristics to account for:
		 * <ul>
		 *   <li> null checks (count the number of times the supposedly leaked obligation
		 *        is compared to null, and subtract those from the leak count)</li>
		 *   <li> field assignments (count number of times obligation type is
		 *        assigned to a field, and subtract those from the leak count)</li>
		 *   <li> return statements (if an instance of the obligation type is
		 *        returned from the method, subtract one from leak count) </li>
		 * </ul>
		 *
		 * @return the adjusted leak count (positive if leaked obligation, negative if
		 *          attempt to release an un-acquired obligation)
		 */
		private int getAdjustedLeakCount(
			State state, int obligationId) throws DataflowAnalysisException, ClassNotFoundException {

			final Obligation obligation = database.getFactory().getObligationById(obligationId);
			Path path = state.getPath();
			PostProcessingPathVisitor visitor = new PostProcessingPathVisitor(obligation, state);
			path.acceptVisitor(cfg, visitor);

			if (visitor.couldNotAnalyze()) {
				return 0;
			} else {
				return visitor.getAdjustedLeakCount();
			}
		}

		private boolean isPossibleInstanceOfObligationType(Subtypes2 subtypes2, ObjectType type, ObjectType obligationType) throws ClassNotFoundException {
			//
			// If we're tracking, e.g., InputStream obligations,
			// and we see a FileInputStream reference being assigned
			// to a field (or returned from a method),
			// then the false-positive supressions heuristic should apply.
			//

			return subtypes2.isSubtype(type, obligationType);
		}

		private void reportPath(
			final BugInstance bugInstance,
			final Obligation obligation,
			final State state) {

			Path path = state.getPath();

			// This PathVisitor will traverse the Path and add appropriate
			// SourceLineAnnotations to the BugInstance.
			PathVisitor visitor = new PathVisitor() {
				boolean sawFirstCreation;
				SourceLineAnnotation lastSourceLine;// = creationSourceLine;
				BasicBlock curBlock;

				public void visitBasicBlock(BasicBlock basicBlock) {
					curBlock = basicBlock;

					// See if the initial instance of the leaked resource
					// is in the  entry fact due to a @WillClose annotation.
					if (curBlock == cfg.getEntry()) {
						// Get the entry fact - it should have precisely one state
						StateSet entryFact = dataflow.getResultFact(curBlock);
						Iterator<State> i = entryFact.stateIterator();
						if (i.hasNext()) {
							State entryState = i.next();
							if (entryState.getObligationSet().getCount(obligation.getId()) > 0) {
								lastSourceLine = SourceLineAnnotation.forFirstLineOfMethod(methodDescriptor);
								lastSourceLine.setDescription(SourceLineAnnotation.ROLE_OBLIGATION_CREATED_BY_WILLCLOSE_PARAMETER);
								bugInstance.add(lastSourceLine);
								sawFirstCreation = true;

								if (REPORT_PATH_DEBUG) {
									System.out.println("  " + obligation + " created by @WillClose parameter at " + lastSourceLine);
								}
							}
						}
					}
				}

				public void visitInstructionHandle(InstructionHandle handle) {
					boolean isCreation = (dataflow.getAnalysis().getActionCache().addsObligation(handle, cpg, obligation));

					if (!sawFirstCreation && !isCreation) {
						return;
					}

					SourceLineAnnotation sourceLine =
						SourceLineAnnotation.fromVisitedInstruction(methodDescriptor, new Location(handle, curBlock));

					boolean isInteresting = (sourceLine.getStartLine() > 0) &&
						(lastSourceLine == null || !sourceLine.equals(lastSourceLine));

					if (REPORT_PATH_DEBUG) {
						System.out.println("  " + handle.getPosition() + " --> " + sourceLine + (isInteresting ? " **" : ""));
					}
					if (isInteresting) {
						sourceLine.setDescription(
							isCreation ? SourceLineAnnotation.ROLE_OBLIGATION_CREATED : SourceLineAnnotation.ROLE_PATH_CONTINUES);
						bugInstance.add(sourceLine);
						lastSourceLine = sourceLine;
						if (isCreation) {
							sawFirstCreation = true;
						}
					}
				}

				public void visitEdge(Edge edge) {
					if (REPORT_PATH_DEBUG) {
						System.out.println("Edge of type " + Edge.edgeTypeToString(edge.getType()) + " to " + edge.getTarget().getLabel());
						if (edge.getTarget().getFirstInstruction() != null) {
							System.out.println("  First instruction in target: " + edge.getTarget().getFirstInstruction());
						}
						if (edge.getTarget().isExceptionThrower()) {
							System.out.println("  exception thrower for " + edge.getTarget().getExceptionThrower());
						}
						if (edge.isExceptionEdge()) {
							System.out.println("  exceptions thrown: " + typeDataflow.getEdgeExceptionSet(edge));
						}
					}
				}
			};

			// Visit the Path
			path.acceptVisitor(cfg, visitor);
		}
	}

	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.Detector#report()
	 */
	public void report() {
		// Nothing to do here
	}

}
